package NEHE.JOGL.Port;

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import javax.swing.*;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.util.FPSAnimator;
import com.sun.opengl.util.texture.*;
import static javax.media.opengl.GL.*;

/**
 * NeHe Lesson 6: Texture
 */
public class NeheJOGL06Texture extends JPanel implements GLEventListener {
   private static final int REFRESH_FPS = 60;    // Display refresh frames per second
   private GLU glu;             // For the GL Utility
   final FPSAnimator animator;  // Used to drive display() 
   
   // Rotational angle about the x, y and z axes in degrees
   private static float angleX = 0;
   private static float angleY = 0;
   private static float angleZ = 0;
   // Rotational speed about x, y, z axes in degrees per refresh
   private static float rotateSpeedX = 0.3f;
   private static float rotateSpeedY = 0.2f;
   private static float rotateSpeedZ = 0.4f;

   // Texture
   private Texture texture;
   private String textureFileName = "images/nehe.png";
   private String textureFileType = ".png";

   // Texture image flips vertically. Shall use TextureCoords class to retrieve the
   // top, bottom, left and right coordinates.
   private float textureTop;
   private float textureBottom;
   private float textureLeft;
   private float textureRight;

   // Constructor
   public NeheJOGL06Texture() {
      GLCanvas canvas = new GLCanvas();
      this.setLayout(new BorderLayout());
      this.add(canvas, BorderLayout.CENTER);
      canvas.addGLEventListener(this);
   
      // Run the animation loop using the fixed-rate Frame-per-second animator,
      // which calls back display() at this fixed-rate (FPS).
      animator = new FPSAnimator(canvas, REFRESH_FPS, true);
   }

   // Main program
   public static void main(String[] args) {
      final int WINDOW_WIDTH = 320;
      final int WINDOW_HEIGHT = 240;
      final String WINDOW_TITLE = "Nehe #6: Texture";

      JFrame frame = new JFrame();
      final NeheJOGL06Texture joglMain = new NeheJOGL06Texture();
      frame.setContentPane(joglMain);
      frame.addWindowListener(new WindowAdapter() {
         @Override 
         public void windowClosing(WindowEvent e) {
            // Use a dedicate thread to run the stop() to ensure that the
            // animator stops before program exits.
            new Thread() {
               @Override 
               public void run() {
                  joglMain.animator.stop(); // stop the animator loop
                  System.exit(0);
               }
            }.start();
         }
      });
      frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
      frame.setTitle(WINDOW_TITLE);
      frame.setVisible(true);
      joglMain.animator.start(); // start the animation loop
   }

   // ------ Implement methods declared in GLEventListener ------

   /**
    * Called back immediately after the OpenGL context is initialized. Can be used 
    * to perform one-time initialization. Run only once.
    */
   @Override
   public void init(GLAutoDrawable drawable) {
      GL gl = drawable.getGL(); // Get the OpenGL graphics context
      glu = new GLU(); // GL Utilities
      gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  // Set background (clear) color
      gl.glClearDepth(1.0f); // Set clear depth value to farthest
      gl.glEnable(GL_DEPTH_TEST); // Enables depth testing
      gl.glDepthFunc(GL_LEQUAL); // The type of depth test to do
      // Do the best perspective correction
      gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
      // Enable smooth shading, which blends colors nicely, and smoothes out lighting.
      gl.glShadeModel(GL_SMOOTH);

      // Load texture from image
      try {
         // Create a OpenGL Texture object from (URL, mipmap, file suffix)
         // Use URL so that can read from JAR and disk file.
         texture = TextureIO.newTexture(
               this.getClass().getResource(textureFileName), false, textureFileType);

         // Use linear filter for texture if image is larger than the original texture
         gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
         // Use linear filter for texture if image is smaller than the original texture
         gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

         // Texture image flips vertically. Shall use TextureCoords class to retrieve
         // the top, bottom, left and right coordinates, instead of using 0.0f and 1.0f.
         TextureCoords textureCoords = texture.getImageTexCoords();
         textureTop = textureCoords.top();
         textureBottom = textureCoords.bottom();
         textureLeft = textureCoords.left();
         textureRight = textureCoords.right();

      } catch (GLException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

   /**
    * Call-back handler for window re-size event. Also called when the drawable is 
    * first set to visible.
    */
   @Override
   public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
      GL gl = drawable.getGL(); // Get the OpenGL graphics context

      if (height == 0) {
         height = 1; // prevent divide by zero
      }
      float aspect = (float)width / height;

      // Set the viewport (display area) to cover the entire window
      gl.glViewport(0, 0, width, height);

      // Setup perspective projection, with aspect ratio matches viewport
      gl.glMatrixMode(GL_PROJECTION); // Choose projection matrix
      gl.glLoadIdentity(); // Reset projection matrix
      glu.gluPerspective(45.0, aspect, 0.1, 100.0); // fovy, aspect, zNear, zFar

      // Enable the model-view transform
      gl.glMatrixMode(GL_MODELVIEW);
      gl.glLoadIdentity(); // reset
   }

   /**
    * Called back by the animator to perform rendering.
    */
   @Override
   public void display(GLAutoDrawable drawable) {
      GL gl = drawable.getGL(); // Get the OpenGL graphics context
      gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers

      // ------ Render a Cube with texture ------
      gl.glLoadIdentity();  // reset the model-view matrix
      gl.glTranslatef(0.0f, 0.0f, -5.0f); // translate into the screen
      gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f); // rotate about the x-axis
      gl.glRotatef(angleY, 0.0f, 1.0f, 0.0f); // rotate about the y-axis
      gl.glRotatef(angleZ, 0.0f, 0.0f, 1.0f); // rotate about the z-axis

      // Enables this texture's target (e.g., GL_TEXTURE_2D) in the current GL
      // context's state.
      texture.enable();
      // gl.glEnable(GL_TEXTURE_2D);
      // gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE);
      // Binds this texture to the current GL context.
      texture.bind();

      gl.glBegin(GL_QUADS);

      // Front Face
      gl.glTexCoord2f(textureLeft, textureBottom);
      gl.glVertex3f(-1.0f, -1.0f, 1.0f); // bottom-left of the texture and quad
      gl.glTexCoord2f(textureRight, textureBottom);
      gl.glVertex3f(1.0f, -1.0f, 1.0f); // bottom-right of the texture and quad
      gl.glTexCoord2f(textureRight, textureTop);
      gl.glVertex3f(1.0f, 1.0f, 1.0f); // top-right of the texture and quad
      gl.glTexCoord2f(textureLeft, textureTop);
      gl.glVertex3f(-1.0f, 1.0f, 1.0f); // top-left of the texture and quad

      // Back Face
      gl.glTexCoord2f(textureRight, textureBottom);
      gl.glVertex3f(-1.0f, -1.0f, -1.0f);
      gl.glTexCoord2f(textureRight, textureTop);
      gl.glVertex3f(-1.0f, 1.0f, -1.0f);
      gl.glTexCoord2f(textureLeft, textureTop);
      gl.glVertex3f(1.0f, 1.0f, -1.0f);
      gl.glTexCoord2f(textureLeft, textureBottom);
      gl.glVertex3f(1.0f, -1.0f, -1.0f);
      
      // Top Face
      gl.glTexCoord2f(textureLeft, textureTop);
      gl.glVertex3f(-1.0f, 1.0f, -1.0f);
      gl.glTexCoord2f(textureLeft, textureBottom);
      gl.glVertex3f(-1.0f, 1.0f, 1.0f);
      gl.glTexCoord2f(textureRight, textureBottom);
      gl.glVertex3f(1.0f, 1.0f, 1.0f);
      gl.glTexCoord2f(textureRight, textureTop);
      gl.glVertex3f(1.0f, 1.0f, -1.0f);
      
      // Bottom Face
      gl.glTexCoord2f(textureRight, textureTop);
      gl.glVertex3f(-1.0f, -1.0f, -1.0f);
      gl.glTexCoord2f(textureLeft, textureTop);
      gl.glVertex3f(1.0f, -1.0f, -1.0f);
      gl.glTexCoord2f(textureLeft, textureBottom);
      gl.glVertex3f(1.0f, -1.0f, 1.0f);
      gl.glTexCoord2f(textureRight, textureBottom);
      gl.glVertex3f(-1.0f, -1.0f, 1.0f);
      
      // Right face
      gl.glTexCoord2f(textureRight, textureBottom);
      gl.glVertex3f(1.0f, -1.0f, -1.0f);
      gl.glTexCoord2f(textureRight, textureTop);
      gl.glVertex3f(1.0f, 1.0f, -1.0f);
      gl.glTexCoord2f(textureLeft, textureTop);
      gl.glVertex3f(1.0f, 1.0f, 1.0f);
      gl.glTexCoord2f(textureLeft, textureBottom);
      gl.glVertex3f(1.0f, -1.0f, 1.0f);
      
      // Left Face
      gl.glTexCoord2f(textureLeft, textureBottom);
      gl.glVertex3f(-1.0f, -1.0f, -1.0f);
      gl.glTexCoord2f(textureRight, textureBottom);
      gl.glVertex3f(-1.0f, -1.0f, 1.0f);
      gl.glTexCoord2f(textureRight, textureTop);
      gl.glVertex3f(-1.0f, 1.0f, 1.0f);
      gl.glTexCoord2f(textureLeft, textureTop);
      gl.glVertex3f(-1.0f, 1.0f, -1.0f);

      gl.glEnd();

      // Disables this texture's target (e.g., GL_TEXTURE_2D) in the current GL
      // context's state.
      //texture.disable();

      // Update the rotational angel after each refresh by the corresponding
      // rotational speed
      angleX += rotateSpeedX;
      angleY += rotateSpeedY;
      angleZ += rotateSpeedZ;
   }

   /**
    * Called back when the display mode (eg. resolution) has been changed.
    * (not implemented by JOGL)
    */
   @Override
   public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
         boolean deviceChanged) {}
}
